package com.gitee.wsl.time.chinese

import com.gitee.wsl.ext.base.currentDateTime
import com.gitee.wsl.text.format.format
import com.gitee.wsl.time.chinese.util.HolidayUtil.getHoliday
import com.gitee.wsl.time.chinese.util.LunarUtil
import com.gitee.wsl.time.chinese.util.LunarUtil.find
import com.gitee.wsl.time.chinese.util.LunarUtil.getJiaZiIndex
import com.gitee.wsl.time.chinese.util.SolarUtil
import com.gitee.wsl.time.chinese.util.SolarUtil.getDaysBetween
import com.gitee.wsl.time.chinese.util.SolarUtil.getDaysOfMonth
import com.gitee.wsl.time.chinese.util.SolarUtil.isLeapYear
import com.gitee.wsl.time.now
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.number
import kotlin.jvm.JvmOverloads
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.round


/**
 * 阳历日期
 *
 * @author 6tail
 */
class Solar {
    /**
     * 获取年份
     *
     * @return 如2015
     */
    /**
     * 年
     */
    val year: Int

    /**
     * 获取月份
     *
     * @return 1到12
     */
    /**
     * 月
     */
    val month: Int

    /**
     * 获取日期
     *
     * @return 1到31之间的数字
     */
    /**
     * 日
     */
    val day: Int

    /**
     * 获取小时
     *
     * @return 0到23之间的数字
     */
    /**
     * 时
     */
    val hour: Int

    /**
     * 获取分钟
     *
     * @return 0到59之间的数字
     */
    /**
     * 分
     */
    val minute: Int

    /**
     * 获取秒钟
     *
     * @return 0到59之间的数字
     */
    /**
     * 秒
     */
    val second: Int

    /**
     * 通过年月日初始化
     *
     * @param year   年
     * @param month  月，1到12
     * @param day    日，1到31
     * @param hour   小时，0到23
     * @param minute 分钟，0到59
     * @param second 秒钟，0到59
     */
    /**
     * 通过年月日初始化
     *
     * @param year  年
     * @param month 月，1到12
     * @param day   日，1到31
     */
    @JvmOverloads
    constructor(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0, second: Int = 0) {
        if (1582 == year && 10 == month) {
            require(day !in 5..<15) { String.format("wrong solar year %d month %d day %d", year, month, day) }
        }
        require(month in 1..12) { String.format("wrong month %d", month) }
        require(day >= 1) { String.format("wrong day %d", day) }
        val days = getDaysOfMonth(year, month)
        require(day <= days) { String.format("only %d days in solar year %d month %d", days, year, month) }
        require(hour in 0..23) { String.format("wrong hour %d", hour) }
        require(minute in 0..59) { String.format("wrong minute %d", minute) }
        require(second in 0..59) { String.format("wrong second %d", second) }
        this.year = year
        this.month = month
        this.day = day
        this.hour = hour
        this.minute = minute
        this.second = second
    }

    /**
     * 通过日期初始化
     *
     * @param date 日期
     */
    /**
     * 默认使用当前日期初始化
     */
    @JvmOverloads
    constructor(date: LocalDateTime = LocalDateTime.now()) {
        year = date.year
        month = date.month.number
        day = date.day
        hour = date.hour
        minute = date.minute
        second = date.second
    }

    constructor(date: LocalDate = LocalDate.now()) {
        year = date.year
        month = date.month.number
        day = date.day
        hour = 0
        minute = 0
        second = 0
    }

    /**
     * 通过儒略日初始化
     *
     * @param julianDay 儒略日
     */
    constructor(julianDay: Double) {
        var d = (julianDay + 0.5).toInt()
        var f = julianDay + 0.5 - d
        val c: Int

        if (d >= 2299161) {
            c = ((d - 1867216.25) / 36524.25).toInt()
            d += 1 + c - (c * 1.0 / 4).toInt()
        }
        d += 1524
        var year = ((d - 122.1) / 365.25).toInt()
        d -= (365.25 * year).toInt()
        var month = (d * 1.0 / 30.601).toInt()
        d -= (30.601 * month).toInt()
        var day = d
        if (month > 13) {
            month -= 13
            year -= 4715
        } else {
            month -= 1
            year -= 4716
        }
        f *= 24.0
        var hour = f.toInt()

        f -= hour.toDouble()
        f *= 60.0
        var minute = f.toInt()

        f -= minute.toDouble()
        f *= 60.0
        var second = round(f).toInt()
        if (second > 59) {
            second -= 60
            minute++
        }
        if (minute > 59) {
            minute -= 60
            hour++
        }
        if (hour > 23) {
            hour -= 24
            day += 1
        }
        this.year = year
        this.month = month
        this.day = day
        this.hour = hour
        this.minute = minute
        this.second = second
    }

    val isLeapYear: Boolean
        /**
         * 是否闰年
         *
         * @return true/false 闰年/非闰年
         */
        get() = isLeapYear(year)

    val week: Int
        /**
         * 获取星期，0代表周日，1代表周一
         *
         * @return 0123456
         */
        get() = ((this.julianDay + 0.5).toInt() + 7000001) % 7

    val weekInChinese: String
        /**
         * 获取星期的中文
         *
         * @return 日一二三四五六
         */
        get() = SolarUtil.WEEK[this.week]

    val festivals: List<String>
        /**
         * 获取节日，有可能一天会有多个节日
         *
         * @return 劳动节等
         */
        get() {
            val l = ArrayList<String>()
            //获取几月几日对应的节日
            var f = SolarUtil.FESTIVAL.get("$month-$day")
            if (null != f) {
                l.add(f)
            }
            //计算几月第几个星期几对应的节日
            val weeks = ceil(day / 7.0).toInt()
            //星期几，0代表星期天
            val week = this.week
            f = SolarUtil.WEEK_FESTIVAL.get("$month-$weeks-$week")
            if (null != f) {
                l.add(f)
            }
            if (day + 7 > getDaysOfMonth(year, month)) {
                f = SolarUtil.WEEK_FESTIVAL.get("$month-0-$week")
                if (null != f) {
                    l.add(f)
                }
            }
            return l
        }

    val otherFestivals: List<String>
        /**
         * 获取非正式的节日，有可能一天会有多个节日
         *
         * @return 非正式的节日列表，如中元节
         */
        get() {
            val l = ArrayList<String>()
            val fs: List<String>? = SolarUtil.OTHER_FESTIVAL.get("$month-$day")
            if (null != fs) {
                l.addAll(fs)
            }
            return l
        }


    val xingZuo: String
        /**
         * 获取星座
         *
         * @return 星座
         */
        get() {
            var index = 11
            val y = month * 100 + day
            if (y in 321..419) {
                index = 0
            } else if (y in 420..520) {
                index = 1
            } else if (y in 521..621) {
                index = 2
            } else if (y in 622..722) {
                index = 3
            } else if (y in 723..822) {
                index = 4
            } else if (y in 823..922) {
                index = 5
            } else if (y in 923..1023) {
                index = 6
            } else if (y in 1024..1122) {
                index = 7
            } else if (y in 1123..1221) {
                index = 8
            } else if (y !in 120..<1222) {
                index = 9
            } else if (y <= 218) {
                index = 10
            }
            return SolarUtil.XINGZUO[index]
        }

    val lunar: Lunar
        /**
         * 获取农历
         *
         * @return 农历
         */
        get() = Lunar(this)

    val julianDay: Double
        /**
         * 获取儒略日
         *
         * @return 儒略日
         */
        get() {
            var y = this.year
            var m = this.month
            val d = this.day + ((this.second * 1.0 / 60 + this.minute) / 60 + this.hour) / 24
            var n = 0
            val g = y * 372 + m * 31 + d.toInt() >= 588829
            if (m <= 2) {
                m += 12
                y--
            }
            if (g) {
                n = (y * 1.0 / 100).toInt()
                n = 2 - n + (n * 1.0 / 4).toInt()
            }
            return (365.25 * (y + 4716)).toInt() + (30.6001 * (m + 1)).toInt() + d + n - 1524.5
        }

    override fun toString(): String {
        return toYmd()
    }

    fun toYmd(): String {
        return String.format("%04d-%02d-%02d", year, month, day)
    }

    fun toYmdHms(): String {
        return toYmd() + " " + String.format("%02d:%02d:%02d", hour, minute, second)
    }

    fun toFullString(): String {
        val s = StringBuilder()
        s.append(toYmdHms())
        if (this.isLeapYear) {
            s.append(" ")
            s.append("闰年")
        }
        s.append(" ")
        s.append("星期")
        s.append(this.weekInChinese)
        for (f in this.festivals) {
            s.append(" (")
            s.append(f)
            s.append(")")
        }
        for (f in this.otherFestivals) {
            s.append(" (")
            s.append(f)
            s.append(")")
        }
        s.append(" ")
        s.append(this.xingZuo)
        s.append("座")
        return s.toString()
    }

    /**
     * 阳历日期相减，获得相差天数
     *
     * @param solar 阳历
     * @return 天数
     */
    fun subtract(solar: Solar): Int {
        return getDaysBetween(solar.year, solar.month, solar.day, year, month, day)
    }

    /**
     * 阳历日期相减，获得相差分钟数
     *
     * @param solar 阳历
     * @return 分钟数
     */
    fun subtractMinute(solar: Solar): Int {
        var days = subtract(solar)
        val cm = hour * 60 + minute
        val sm = solar.hour * 60 + solar.minute
        var m = cm - sm
        if (m < 0) {
            m += 1440
            days--
        }
        m += days * 1440
        return m
    }

    /**
     * 是否在指定日期之后
     *
     * @param solar 阳历
     * @return true/false
     */
    fun isAfter(solar: Solar): Boolean {
        if (year > solar.year) {
            return true
        }
        if (year < solar.year) {
            return false
        }
        if (month > solar.month) {
            return true
        }
        if (month < solar.month) {
            return false
        }
        if (day > solar.day) {
            return true
        }
        if (day < solar.day) {
            return false
        }
        if (hour > solar.hour) {
            return true
        }
        if (hour < solar.hour) {
            return false
        }
        if (minute > solar.minute) {
            return true
        }
        if (minute < solar.minute) {
            return false
        }
        return second > solar.second
    }

    /**
     * 是否在指定日期之前
     *
     * @param solar 阳历
     * @return true/false
     */
    fun isBefore(solar: Solar): Boolean {
        if (year > solar.year) {
            return false
        }
        if (year < solar.year) {
            return true
        }
        if (month > solar.month) {
            return false
        }
        if (month < solar.month) {
            return true
        }
        if (day > solar.day) {
            return false
        }
        if (day < solar.day) {
            return true
        }
        if (hour > solar.hour) {
            return false
        }
        if (hour < solar.hour) {
            return true
        }
        if (minute > solar.minute) {
            return false
        }
        if (minute < solar.minute) {
            return true
        }
        return second < solar.second
    }

    /**
     * 年推移
     *
     * @param years 年数
     * @return 阳历
     */
    fun nextYear(years: Int): Solar {
        val y = year + years
        val m = month
        var d = day
        if (1582 == y && 10 == m) {
            if (d in 5..<15) {
                d += 10
            }
        } else if (2 == m) {
            if (d > 28) {
                if (!isLeapYear(y)) {
                    d = 28
                }
            }
        }
        return fromYmdHms(y, m, d, hour, minute, second)
    }

    /**
     * 月推移
     *
     * @param months 月数
     * @return 阳历
     */
    fun nextMonth(months: Int): Solar {
        val month = SolarMonth.fromYm(year, this.month).next(months)
        val y = month.year
        val m = month.month
        var d = day
        if (1582 == y && 10 == m) {
            if (d in 5..<15) {
                d += 10
            }
        } else {
            val maxDay = getDaysOfMonth(y, m)
            if (d > maxDay) {
                d = maxDay
            }
        }
        return fromYmdHms(y, m, d, hour, minute, second)
    }

    /**
     * 获取往后推几天的阳历日期，如果要往前推，则天数用负数
     *
     * @param days 天数
     * @return 阳历日期
     */
    fun next(days: Int): Solar {
        var y = year
        var m = month
        var d = day
        if (1582 == y && 10 == m) {
            if (d > 4) {
                d -= 10
            }
        }
        if (days > 0) {
            d += days
            var daysInMonth = getDaysOfMonth(y, m)
            while (d > daysInMonth) {
                d -= daysInMonth
                m++
                if (m > 12) {
                    m = 1
                    y++
                }
                daysInMonth = getDaysOfMonth(y, m)
            }
        } else if (days < 0) {
            while (d + days <= 0) {
                m--
                if (m < 1) {
                    m = 12
                    y--
                }
                d += getDaysOfMonth(y, m)
            }
            d += days
        }
        if (1582 == y && 10 == m) {
            if (d > 4) {
                d += 10
            }
        }
        return fromYmdHms(y, m, d, hour, minute, second)
    }

    /**
     * 取往后推几天的阳历日期，如果要往前推，则天数用负数
     *
     * @param days        天数
     * @param onlyWorkday 是否仅限工作日
     * @return 阳历日期
     */
    fun next(days: Int, onlyWorkday: Boolean): Solar {
        if (!onlyWorkday) {
            return next(days)
        }
        var solar = fromYmdHms(year, month, day, hour, minute, second)
        if (days != 0) {
            var rest = abs(days)
            val add = if (days < 0) -1 else 1
            while (rest > 0) {
                solar = solar.next(add)
                var work = true
                val holiday = getHoliday(solar.year, solar.month, solar.day)
                if (null == holiday) {
                    val week = solar.week
                    if (0 == week || 6 == week) {
                        work = false
                    }
                } else {
                    work = holiday.isWork
                }
                if (work) {
                    rest -= 1
                }
            }
        }
        return solar
    }

    /**
     * 小时推移
     *
     * @param hours 小时数
     * @return 阳历
     */
    fun nextHour(hours: Int): Solar {
        val h = hour + hours
        val n = if (h < 0) -1 else 1
        var hour = abs(h)
        var days = hour / 24 * n
        hour = (hour % 24) * n
        if (hour < 0) {
            hour += 24
            days--
        }
        val solar = next(days)
        return fromYmdHms(solar.year, solar.month, solar.day, hour, solar.minute, solar.second)
    }

    val salaryRate: Int
        /**
         * 获取薪资比例(感谢 https://gitee.com/smr1987)
         *
         * @return 薪资比例：1/2/3
         */
        get() {
            // 元旦节
            if (month == 1 && day == 1) {
                return 3
            }
            // 劳动节
            if (month == 5 && day == 1) {
                return 3
            }
            // 国庆
            if (month == 10 && day >= 1 && day <= 3) {
                return 3
            }
            val lunar: Lunar = this.lunar
            // 春节
            if (lunar.month== 1 && lunar.day >= 1 && lunar.day <= 3) {
                return 3
            }
            // 端午
            if (lunar.month== 5 && lunar.day == 5) {
                return 3
            }
            // 中秋
            if (lunar.month== 8 && lunar.day == 15) {
                return 3
            }
            // 清明
            if ("清明" == lunar.getJieQi()) {
                return 3
            }
            val holiday = getHoliday(year, month, day)
            if (null != holiday) {
                // 法定假日非上班
                if (!holiday.isWork) {
                    return 2
                }
            } else {
                // 周末
                val week = this.week
                if (week == 6 || week == 0) {
                    return 2
                }
            }
            // 工作日
            return 1
        }

    companion object {
        /**
         * 时区
         */
        val TIME_ZONE: TimeZone = TimeZone.of("GMT+8")

        /**
         * 2000年儒略日数(2000-1-1 12:00:00 UTC)
         */
        const val J2000: Double = 2451545.0

        /**
         * 通过指定日期获取阳历
         *
         * @param date 日期
         * @return 阳历
         */
        fun fromDate(date: LocalDate): Solar {
            return Solar(date)
        }

        fun fromDate(date: LocalDateTime): Solar {
            return Solar(date)
        }


        /**
         * 通过指定儒略日获取阳历
         *
         * @param julianDay 儒略日
         * @return 阳历
         */
        fun fromJulianDay(julianDay: Double): Solar {
            return Solar(julianDay)
        }

        /**
         * 通过指定年月日获取阳历
         *
         * @param year  年
         * @param month 月，1到12
         * @param day   日，1到31
         * @return 阳历
         */
        fun fromYmd(year: Int, month: Int, day: Int): Solar {
            return Solar(year, month, day)
        }

        /**
         * 通过指定年月日时分获取阳历
         *
         * @param year   年
         * @param month  月，1到12
         * @param day    日，1到31
         * @param hour   小时，0到23
         * @param minute 分钟，0到59
         * @param second 秒钟，0到59
         * @return 阳历
         */
        fun fromYmdHms(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int): Solar {
            return Solar(year, month, day, hour, minute, second)
        }

        /**
         * 通过八字获取阳历列表（晚子时日柱按当天，起始年为1900）
         *
         * @param yearGanZhi  年柱
         * @param monthGanZhi 月柱
         * @param dayGanZhi   日柱
         * @param timeGanZhi  时柱
         * @return 符合的阳历列表
         */
        fun fromBaZi(yearGanZhi: String, monthGanZhi: String, dayGanZhi: String, timeGanZhi: String): List<Solar> {
            return fromBaZi(yearGanZhi, monthGanZhi, dayGanZhi, timeGanZhi, 2)
        }

        /**
         * 通过八字获取阳历列表（起始年为1900）
         *
         * @param yearGanZhi  年柱
         * @param monthGanZhi 月柱
         * @param dayGanZhi   日柱
         * @param timeGanZhi  时柱
         * @param sect        流派，2晚子时日柱按当天，1晚子时日柱按明天
         * @return 符合的阳历列表
         */
        fun fromBaZi(
            yearGanZhi: String,
            monthGanZhi: String,
            dayGanZhi: String,
            timeGanZhi: String,
            sect: Int,
        ): List<Solar> {
            return fromBaZi(yearGanZhi, monthGanZhi, dayGanZhi, timeGanZhi, sect, 1900)
        }

        /**
         * 通过八字获取阳历列表
         *
         * @param yearGanZhi  年柱
         * @param monthGanZhi 月柱
         * @param dayGanZhi   日柱
         * @param timeGanZhi  时柱
         * @param sect        流派，2晚子时日柱按当天，1晚子时日柱按明天
         * @param baseYear    起始年
         * @return 符合的阳历列表
         */
        fun fromBaZi(
            yearGanZhi: String,
            monthGanZhi: String,
            dayGanZhi: String,
            timeGanZhi: String,
            sect: Int,
            baseYear: Int,
        ): List<Solar> {
            var sect = sect
            sect = if (1 == sect) 1 else 2
            val l = ArrayList<Solar>()
            // 月地支距寅月的偏移值
            var m = find(monthGanZhi.substring(1), LunarUtil.ZHI, -1) - 2
            if (m < 0) {
                m += 12
            }
            // 月天干要一致
            if (((find(
                    yearGanZhi.substring(0, 1),
                    LunarUtil.GAN,
                    -1
                ) + 1) * 2 + m) % 10 != find(monthGanZhi.substring(0, 1), LunarUtil.GAN, -1)
            ) {
                return l
            }
            // 1年的立春是辛酉，序号57
            var y = getJiaZiIndex(yearGanZhi) - 57
            if (y < 0) {
                y += 60
            }
            y++
            // 节令偏移值
            m *= 2
            // 时辰地支转时刻，子时按零点算
            val h = find(timeGanZhi.substring(1), LunarUtil.ZHI, -1) * 2
            var hours = intArrayOf(h)
            if (0 == h && 2 == sect) {
                hours = intArrayOf(0, 23)
            }
            val startYear = baseYear - 1

            // 结束年
//            val c: Calendar = Calendar.getInstance(TIME_ZONE)
//            c.setTime(Date())
//            c.set(Calendar.MILLISECOND, 0)
//            val endYear: Int = c.get(Calendar.YEAR)
            val endYear: Int = currentDateTime.year


            while (y <= endYear) {
                if (y >= startYear) {
                    // 立春为寅月的开始
                    val jieQiList = ArrayList(Lunar.fromYmd(y, 1, 1).jieQiTable.values)
                    // 节令推移，年干支和月干支就都匹配上了
                    var solarTime: Solar = jieQiList.get(4 + m)
                    if (solarTime.year >= baseYear) {
                        // 日干支和节令干支的偏移值
                        var d = getJiaZiIndex(dayGanZhi) - getJiaZiIndex(solarTime.lunar.dayInGanZhiExact2)
                        if (d < 0) {
                            d += 60
                        }
                        if (d > 0) {
                            // 从节令推移天数
                            solarTime = solarTime.next(d)
                        }
                        for (hour in hours) {
                            var mi = 0
                            var s = 0
                            if (d == 0 && hour == solarTime.hour) {
                                // 如果正好是节令当天，且小时和节令的小时数相等的极端情况，把分钟和秒钟带上
                                mi = solarTime.minute
                                s = solarTime.second
                            }
                            // 验证一下
                            var solar = fromYmdHms(
                                solarTime.year,
                                solarTime.month,
                                solarTime.day, hour, mi, s
                            )
                            if (d == 30) {
                                solar = solar.nextHour(-1)
                            }
                            val lunar: Lunar = solar.lunar
                            val dgz: String =
                                if (2 == sect) lunar.dayInGanZhiExact2 else lunar.dayInGanZhiExact
                            if (lunar.yearInGanZhiExact.equals(yearGanZhi) && lunar.monthInGanZhiExact
                                    .equals(monthGanZhi) && dgz == dayGanZhi && lunar.timeInGanZhi
                                    .equals(timeGanZhi)
                            ) {
                                l.add(solar)
                            }
                        }
                    }
                }
                y += 60
            }
            return l
        }
    }
}
